"""
Parses files in the ISCAS 89 format for our simulator

Format of files: 
Comments
PO & PI 
Blank Line
Logic Gates

Returns:
List of PIs 
List of POs

@author Will Huang
"""

import sys
from Gates.Gate import Gate
from Gates.ANDGate import AndGate
from Gates.NANDGate import NandGate
from Gates.NORGate import NorGate
from Gates.NOTGate import NotGate
from Gates.ORGate import OrGate
from Gates.XNORGate import XnorGate
from Gates.XORGate import XorGate

from Wire import *

def parse(filename):
	lineNum = 0
	rawfile = open(filename, 'rb')
	pInputs = [] #Primary inputs
	pOutputs = [] #Primary outputs 
	pOutputNames = [] #Primary output names. Used to avoid creating primary inputs at the very start
	wires = [[]] #list of lists signifying levels. Level = level of source gate, or source wire + 1. Primary Inputs = level 0
	gates = [[]] #list of lists signifying levels. Level = level of highest level input wire + 1
	wireFinder = {}
	bufFinder = {}

	for row in rawfile: 
		line = str(row).lower()
		line = line.strip()
		lineNum += 1

		if len(line) > 5 and line[0] != '#':
			if 'input' in line:
				lineSplit = line.split( '(' )
				pInputName = lineSplit[1].strip( ')' )
				addWire = Wire(name = pInputName, level = 0, lineNum = lineNum)
				addWire.zeroControllability = 1
				addWire.oneControllability = 1
				pInputs.append(addWire)
				wires[0].append(addWire)
				wireFinder[pInputName] = addWire

			elif 'output' in line:
				lineSplit = line.split( '(' )
				outWireName = lineSplit[1].strip( ')')
				pOutputNames.append(outWireName)

			elif '=' in line and 'buf(' in line:
				lineSplit = line.split()
				bufout = lineSplit[0]
				inWireName = lineSplit[2].split('(')[1].strip(')')

				if inWireName in bufFinder: 
					inWire = bufFinder[inWireName]
				else: 
					inWire = wireFinder[inWireName]

				if bufout in pOutputNames:
					pOutputs.append(inWire)
					inWire.observability = 0
				else: 
					bufFinder[bufout] = inWire
				
			elif '=' in line: # Start of actual gates
				lineSplit = line.split()
				outputName = lineSplit[0]
				gateName = outputName + '_gate'
				gateWires = lineSplit[2:len(lineSplit)]
				gateType = gateWires[0].split( '(' )
				gateWires[0] = gateType[1]
				gateType = gateType[0]
				for index in range(len(gateWires)):
					gateWires[index] = gateWires[index].strip(' ,)')
				inputLineMaxLevel = 0
				inputLines = []
				"""
				Get inputs to gate. 
				Create new wires in case of fanout
				"""
				for index in range(len(gateWires)):
					a = gateWires[index]
					if a in bufFinder:
						a = bufFinder[a]
					else: 	
						a = wireFinder[a]
					inWire = ""
					if len(a.destinations) > 0: #Handling fanout
						if isinstance(a.destinations[0], Gate):
							nWire = Wire(name = a.name + '_' + 'nWire', fromLineOrGate = a, level = a.level, lineNum = lineNum)
							nWire.destinations.append(a.destinations[0])
							nWire.zeroControllability = a.zeroControllability + 1
							nWire.oneControllability = a.oneControllability + 1
							a.destinations[0].inputs.append(nWire)
							a.destinations[0].inputs.remove(a)
							a.destinations[0] = nWire
							wireFinder[nWire.name] = nWire
							wires[nWire.level].append(nWire)

						inWire = Wire(name = a.name + '_' + str(lineNum), fromLineOrGate = a, level = a.level, lineNum = lineNum)
						inWire.zeroControllability = a.zeroControllability + 1
						inWire.oneControllability = a.oneControllability + 1
						a.destinations.append(inWire)
						wireFinder[inWire.name] = inWire
						wires[inWire.level].append(inWire)

					else:
						inWire = a

					inputLines.append(inWire)
					if inWire.level > inputLineMaxLevel:
						inputLineMaxLevel = inWire.level
				
				outWire = Wire(name = outputName, level = inputLineMaxLevel + 1, lineNum = lineNum)
				wireFinder[outputName] = outWire
				gate = makeGate(gateType, gateName, inputLineMaxLevel, inputLines, outWire)
				outWire.source = gate

				for lines in gate.inputs:
					lines.destinations.append(gate)
				if outWire.name in pOutputNames:
					pOutputs.append(outWire)
					outWire.observability = 0

				if len(wires) < outWire.level + 1:
					wires.append([])
				wires[outWire.level].append(outWire)
				
				if len(gates) < gate.level:
					gates.append([])
				gates[gate.level - 1].append(gate)
	
	rawfile.close()
	setObservabilities(pOutputs)
	return pInputs, pOutputs, wires, gates
	

def makeGate(gateType, gateName, inputLineMaxLevel, inputLines, outWire):
	gate2 = ""
	
	if gateType == 'and':
		gate2 = AndGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire) 
	elif gateType == 'nand':
		gate2 = NandGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)
	elif gateType == 'nor':
		gate2 = NorGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)
	elif gateType == 'not':
		gate2 = NotGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)
	elif gateType == 'or':
		gate2 = OrGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)
	elif gateType == 'xnor':
		gate2 = XnorGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)
	elif gateType == 'xor':
		gate2 = XorGate(gateName = gateName, level = inputLineMaxLevel + 1, inputLines = inputLines, outputLine = outWire)

	setGateOutputControllabilities(gate2)
	return gate2


def setGateOutputControllabilities(gate):
	outWire = gate.output

	if type(gate) is AndGate:
		oneControl = 1
		for wire in gate.inputs: 
			if wire.zeroControllability + 1 < outWire.zeroControllability:
				outWire.zeroControllability = wire.zeroControllability + 1 
			oneControl += wire.oneControllability
		outWire.oneControllability = oneControl
	elif type(gate) is NandGate:
		zeroControl = 1
		for wire in gate.inputs: 
			if wire.zeroControllability + 1 < outWire.oneControllability:
				outWire.oneControllability = wire.zeroControllability + 1
			zeroControl += wire.oneControllability
		outWire.zeroControllability = zeroControl
	elif type(gate) is NorGate:
		oneControl = 1
		for wire in gate.inputs: 
			if wire.oneControllability + 1 < outWire.zeroControllability:
				outWire.zeroControllability = wire.oneControllability + 1
			oneControl += wire.zeroControllability
		outWire.oneControllability = oneControl
	elif type(gate) is NotGate:
		outWire.zeroControllability = gate.inputs[0].oneControllability + 1
		outWire.oneControllability = gate.inputs[0].zeroControllability + 1
	elif type(gate) is OrGate:
		zeroControl = 1
		for wire in gate.inputs: 
			if wire.oneControllability + 1 < outWire.oneControllability:
				outWire.oneControllability = wire.oneControllability + 1
			zeroControl += wire.zeroControllability
		outWire.zeroControllability = zeroControl
	elif type(gate) is XnorGate: 
		sInputs = sorted(gate.inputs, key = lambda wire: abs(wire.zeroControllability - wire.oneControllability))
		zeroInputs = []
		oneInputs = []
		baseControl = 1
		lastZero = 0
		lastOne = 1
		for item in sInputs: 
			if item is sInputs[-1]:
				if len(oneInputs) % 2 == 0:
					lastZero = item.zeroControllability
					lastOne = item.oneControllability
				else:
					lastZero = item.oneControllability
					lastOne = item.zeroControllability
			elif item.zeroControllability < item.oneControllability: 
				zeroInputs.append(item)
			else: 
				oneInputs.append(item)
		for wire in zeroInputs:
			baseControl += wire.zeroControllability
		for wire in oneInputs:
			baseControl += wire.oneControllability
		outWire.zeroControllability = baseControl + lastZero
		outWire.oneControllability = baseControl + lastOne
	elif type(gate) is XorGate:
		sInputs = sorted(gate.inputs, key = lambda wire: abs(wire.zeroControllability - wire.oneControllability))
		zeroInputs = []
		oneInputs = []
		baseControl = 1
		lastZero = 0
		lastOne = 1
		for item in sInputs: 
			if item is sInputs[-1]:
				if len(oneInputs) % 2 == 0:
					lastOne = item.zeroControllability
					lastZero = item.oneControllability
				else:
					lastOne = item.oneControllability
					lastZero = item.zeroControllability
			elif item.zeroControllability < item.oneControllability: 
				zeroInputs.append(item)
			else: 
				oneInputs.append(item)
		for wire in zeroInputs:
			baseControl += wire.zeroControllability
		for wire in oneInputs:
			baseControl += wire.oneControllability
		outWire.zeroControllability = baseControl + lastZero
		outWire.oneControllability = baseControl + lastOne


"""
This function would be much prettier recursively, but
python doesn't support tail recursion, so changing it 
to a while loops saves a little time

outputs are the primary outputs 
"""
def setObservabilities(outputs):
	outs = []
	for line in outputs:
		if type(line.source) is Wire: 
			if line.source.observability > line.observability + 1: 
				line.source.observability = line.observability + 1
			outs.append(line.source)
		elif isinstance(line.source, Gate):
			setGateInputObservabilities(line.source)
			for wire in line.source.inputs:
				outs.append(wire)

	if len(outs) == 0:
		"print setObserv wtf. Circuit is only primary outputs?"
		return False

	while outs:
		next = []
		for line in outs:
			if type(line.source) is Wire: 
				if line.source.observability > line.observability + 1: 
					line.source.observability = line.observability + 1
				if line.source not in next: #Without this line, gates with an output that fans out get evaluated multiple times
					next.append(line.source)
			elif isinstance(line.source, Gate):
				setGateInputObservabilities(line.source)
				for wire in line.source.inputs:
					next.append(wire)
		outs = next 


def driveObservabilityAcrossWires(wire):
	if type(wire.source) is Wire: 
		wire.source.observability = wire.observability + 1
		driveObservabilityAcrossWires(wire.source)
	else:
		return wire

def setGateInputObservabilities(gate):
	outWire = gate.output
	inputs = gate.inputs
	observ = outWire.observability + 1

	if type(gate) is AndGate or type(gate) is NandGate:
		for line in inputs: 
			observ += line.oneControllability
		for line2 in inputs: 
			line2.observability = observ - line2.oneControllability

	elif type(gate) is NorGate or type(gate) is OrGate:
		for line in inputs: 
			observ += line.zeroControllability
		for line2 in inputs: 
			line2.observability = observ - line2.zeroControllability

	elif type(gate) is NotGate:
		inputs[0].observability = observ

	elif type(gate) is XnorGate or type(gate) is XorGate:
		for line in inputs: 
			if line.zeroControllability < line.oneControllability:
				observ += line.zeroControllability
			else: 
				observ += line.oneControllability
		for line in inputs: 
			if line.zeroControllability < line.oneControllability:
				line.observability = observ - line.zeroControllability
			else: 
				line.observability = observ - line.oneControllability

"""
filename = "ISCAS85/c1908.bench"
pInputs, pOutputs, wires, gates = parse(filename)
print "parseDone"


print "Inputs: " 
for count in range(len(pInputs)):
	pInputs[count].printSelf()

print "----------------------------"
print "Outputs: "
for count in range(len(pOutputs)):
	pOutputs[count].printSelf()

print "----------------------------"
"""
"""
for count in range(len(wires)):
	for index in range(len(wires[count])):
		wires[count][index].printSelf()

for count in range(len(gates)):
	for index in range(len(gates[count])):
		gates[count][index].printSelf()
"""